The goal of this notebook is to compare label transfer results
between:
- Label transfer code with Azimuth currently in
main at
commit 6af112d. These results are referred to as
"azimuth".
- Label transfer code adapted from Azimuth. These results are referred
to as
"adapted_azimuth".
Setup
knitr::opts_chunk$set(message = FALSE, warning = FALSE)
options(future.globals.maxSize = 891289600000000)
suppressPackageStartupMessages({
library(tidyverse)
library(patchwork)
library(Seurat)
})
repository_base <- rprojroot::find_root(rprojroot::is_git_root)
module_base <- file.path(repository_base, "analyses", "cell-type-wilms-tumor-06")
result_dir <- file.path(module_base, "results")
# functions to perform label transfer with azimuth-adapted approach
source(
file.path(module_base, "notebook_template", "utils", "label-transfer-functions.R")
)
# Output files
full_results_file <- file.path(module_base, "scratch", "compare-label-transfer_fetal-full.rds")
kidney_results_file <- file.path(module_base, "scratch", "compare-label-transfer_fetal-kidney.rds")
Functions
# Make a heatmap of counts for label transfer strategies
plot_count_heatmap <- function(df, title, sample_id) {
all_preds <- union(df$azimuth, df$adapted_azimuth)
plotme <- data.frame(
azimuth = all_preds,
adapted_azimuth = all_preds
) |>
expand(azimuth, adapted_azimuth) |>
mutate(n = NA_integer_) |>
anti_join(distinct(df)) |>
bind_rows(
df |> count(azimuth, adapted_azimuth)
) |>
arrange(azimuth) |>
mutate(
color = case_when(
is.na(n) ~ "white",
n <= 20 ~ "grey90",
n <= 50 ~ "lightblue",
n <= 100 ~ "cornflowerblue",
n <= 500 ~ "red",
n <= 1000 ~ "yellow2",
.default = "yellow"
)
)
ggplot(plotme) +
aes(x = azimuth, y = adapted_azimuth, fill = color, label = n) +
geom_tile(alpha = 0.5) +
geom_abline(color = "firebrick", alpha = 0.5) +
geom_text(size = 3.5) +
#scale_fill_viridis_c(name = "count", na.value = "grey90") +
scale_fill_identity() +
theme_bw() +
theme(axis.text.y = element_text(size = 7),
axis.text.x = element_text(angle = 30, size = 7, hjust=1),
legend.position = "bottom",
legend.title = element_text(size = 9),
legend.text = element_text(size = 8)) +
labs(
title = glue::glue("{sample_id}: {str_to_title(title)}")
)
}
# Wrapper function to compare results between approaches
# Makes two plots:
# - heatmap comparing counts for cell labels between approaches
# - density plot of annotation scores for labels that agree and disagree between approaches
compare <- function(df, compare_column, score_column, title) {
spread_df <- df |>
select({{compare_column}}, barcode, version) |>
pivot_wider(names_from = version, values_from = {{compare_column}})
heatmap <- plot_count_heatmap(spread_df, title, unique(df$sample_id))
disagree_barcodes <- spread_df |>
filter(azimuth != adapted_azimuth) |>
pull(barcode)
df2 <- df |>
mutate(
agree = ifelse(barcode %in% disagree_barcodes, "labels disagree", "labels agree"),
agree = fct_relevel(agree, "labels disagree", "labels agree")
)
density_plot <- ggplot(df2) +
aes(x = {{score_column}}, fill = agree) +
geom_density(alpha = 0.6) +
theme_bw() +
ggtitle(
glue::glue("Disagree count: {length(disagree_barcodes)} out of {nrow(spread_df)}")
) +
theme(legend.position = "bottom")
print(heatmap + density_plot + plot_layout(widths = c(2, 1)))
}
Label transfer
This section both:
- Reads in existing Azimuth label transfer results
- Performs label transfer with Azimuth-adapted approach
If results are already available, we read in the files rather than
regenerating results.
# sample ids to process
sample_ids <- c("SCPCS000179", "SCPCS000184", "SCPCS000194", "SCPCS000205", "SCPCS000208")
# read in seurat input objects, as needed
if ((!file.exists(full_results_file)) || (!file.exists(kidney_results_file))) {
srat_objects <- sample_ids |>
purrr::map(
\(id) {
srat <- readRDS(
file.path(result_dir, id, glue::glue("01-Seurat_{id}.Rds")
))
DefaultAssay(srat) <- "RNA"
return(srat)
})
names(srat_objects) <- sample_ids
}
Label transfer for fetal full
if (!file.exists(full_results_file)) {
# read reference
ref <- readRDS(file.path(
module_base,
"results",
"references",
"cao_formatted_ref.rds"
))
full_reference <- ref$reference
full_refdata <- ref$refdata
full_dims <- ref$dims
full_annotation_columns <- c(
glue::glue("predicted.{ref$annotation_levels}"),
glue::glue("predicted.{ref$annotation_levels}.score")
)
fetal_full <- srat_objects |>
purrr::imap(
\(srat, id) {
# Perform label transfer with new code
set.seed(params$seed)
query <- prepare_query(srat, rownames(full_reference), file.path(module_base, "scratch", "homologs.rds"))
query <- transfer_labels(
query,
full_reference,
full_dims,
full_refdata
)
# Read in results from existing Azimuth label transfer code
srat_02a <- readRDS(
file.path(result_dir, id, glue::glue("02a-fetal_full_label-transfer_{id}.Rds"))
)
# create final data frame with all annotations
query@meta.data[, full_annotation_columns] |>
tibble::rownames_to_column(var = "barcode") |>
mutate(
sample_id = id,
version = "adapted_azimuth"
) |>
# existing results
bind_rows(
data.frame(
sample_id = id,
barcode = colnames(srat_02a),
version = "azimuth",
predicted.annotation.l1 = srat_02a$fetal_full_predicted.annotation.l1,
predicted.annotation.l1.score = srat_02a$fetal_full_predicted.annotation.l1.score,
predicted.annotation.l2 = srat_02a$fetal_full_predicted.annotation.l2,
predicted.annotation.l2.score = srat_02a$fetal_full_predicted.annotation.l2.score,
predicted.organ = srat_02a$fetal_full_predicted.organ,
predicted.organ.score = srat_02a$fetal_full_predicted.organ.score
)
)
}
)
write_rds(fetal_full, full_results_file)
} else {
fetal_full <- read_rds(full_results_file)
}
Label transfer for fetal kidney
if (!file.exists(kidney_results_file)) {
# read reference
ref <- readRDS(file.path(
module_base,
"results",
"references",
"stewart_formatted_ref.rds"
))
# Pull out information from the reference object we need for label transfer
kidney_reference <- ref$reference
kidney_refdata <- ref$refdata
kidney_dims <- ref$dims
kidney_annotation_columns <- c(
glue::glue("predicted.{ref$annotation_levels}"),
glue::glue("predicted.{ref$annotation_levels}.score")
)
fetal_kidney <- srat_objects |>
purrr::imap(
\(srat, id) {
# Perform label transfer with new code
set.seed(params$seed)
query <- prepare_query(srat, rownames(kidney_reference), file.path(module_base, "scratch", "homologs.rds"))
query <- transfer_labels(
query,
kidney_reference,
kidney_dims,
kidney_refdata
)
# Read in results from existing Azimuth label transfer code
srat_02b <- readRDS(
file.path(result_dir, id, glue::glue("02b-fetal_kidney_label-transfer_{id}.Rds"))
)
# create final data frame with all annotations
query@meta.data[, kidney_annotation_columns] |>
tibble::rownames_to_column(var = "barcode") |>
mutate(
sample_id = id,
version = "adapted_azimuth"
) |>
# existing results
bind_rows(
data.frame(
sample_id = id,
barcode = colnames(srat_02b),
version = "azimuth",
predicted.compartment = srat_02b$fetal_kidney_predicted.compartment,
predicted.compartment.score = srat_02b$fetal_kidney_predicted.compartment.score,
predicted.cell_type = srat_02b$fetal_kidney_predicted.cell_type,
predicted.cell_type.score = srat_02b$fetal_kidney_predicted.cell_type.score
)
)
}
)
write_rds(fetal_kidney, kidney_results_file)
} else {
fetal_kidney <- read_rds(kidney_results_file)
}
Compare results
We expect: - The majority of annotations match between approaches,
with heatmap counts primarily falling along the diagonal - Any
annotations that disagree should have low scores
Fetal full reference
Note that results from the L2 reference are not plotted because they
are not used in cell type annotation.
fetal_full |>
purrr::walk(
\(dat) {
compare(dat, predicted.annotation.l1, predicted.annotation.l1.score, "l1")
compare(dat, predicted.organ, predicted.organ.score, "organ")
}
)










Fetal kidney reference
fetal_kidney |>
purrr::walk(
\(dat) {
compare(dat, predicted.compartment, predicted.compartment.score, "compartment")
compare(dat, predicted.cell_type, predicted.cell_type.score, "cell_type")
}
)










Conclusions
The vast majority of the time, labels agree. Generally speaking, when
labels do not agree, their annotation scores are much lower, which is as
expected.
Additional notable differences are shown in tables below:
Fetal full reference:
- The Azimuth-adapted approach occasionally calls kidney or
kidney-related cells as intestine or intestine epithelial
- Some other kidney-related differences are noted:
| SCPSC000179 |
L1 |
70 |
Metanephritic cells |
Intestinal epithelial cells |
| SCPSC000179 |
Organ |
64 |
Kidney |
Intestine |
| SCPSC000179 |
Organ |
20 |
Lung |
Kidney |
| SCPSC000194 |
L1 |
60 |
Stromal cells |
Mesangial cells |
| SCPSC000194 |
Organ |
35 |
Kidney |
Intestine |
| SCPSC000194 |
Organ |
36 |
Lung |
Kidney |
| SCPSC000205 |
Organ |
56 |
Kidney |
Intestine |
| SCPSC000208 |
L1 |
101 |
Mesangial cells |
Metanephritic cells |
| SCPSC000208 |
L1 |
101 |
Intestinal epithelial cells |
Metanephritic cells |
| SCPSC000208 |
Organ |
149 |
Kidney |
Intestine |
Fetal kidney reference:
- There are a small but notable number of cells flipped between
mesenchyme and kidney cells, in particular for sample SCPSC000184. This
is the main discrepancy.
- Most of the cell type differences are not necessarily biologically
meaningful for our purposes, as listed below. These are not noted in the
table.
kidney cell vs podocyte
kidney epithelial cell vs kidney cell
mesenchymal cell vs
mesenchymal stem cell
- There are a decent number of times when stroma and fetal nephron are
flipped, but this makes sense given that we expect many of the stroma
may be tumor.
- Disagreeing annotation scores when using the cell type reference
were often higher, but many of the disagreements for this reference were
not meaningful (e.g.
kidney epithelial cell vs
kidney cell).
| SCPSC000179 |
cell type |
202 |
mesenchymal cell |
kidney epithelial cell |
| SCPSC000184 |
compartment |
111 |
fetal nephron |
stroma |
| SCPSC000184 |
cell type |
536 |
kidney epithelial cell |
mesenchymal cell |
| SCPSC000194 |
compartment |
565 |
fetal nephron |
stroma |
| SCPSC000194 |
cell type |
89 |
kidney epithelial cell |
mesenchymal cell |
| SCPSC000205 |
compartment |
684 |
fetal nephron |
stroma |
| SCPSC000208 |
compartment |
2111 |
fetal nephron |
stroma |
Session Info
sessionInfo()
R version 4.4.1 (2024-06-14)
Platform: aarch64-apple-darwin20
Running under: macOS 15.1
Matrix products: default
BLAS: /System/Library/Frameworks/Accelerate.framework/Versions/A/Frameworks/vecLib.framework/Versions/A/libBLAS.dylib
LAPACK: /Library/Frameworks/R.framework/Versions/4.4-arm64/Resources/lib/libRlapack.dylib; LAPACK version 3.12.0
locale:
[1] en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8
time zone: America/New_York
tzcode source: internal
attached base packages:
[1] stats graphics grDevices datasets utils methods base
other attached packages:
[1] Seurat_5.1.0 SeuratObject_5.0.2 sp_2.1-4 patchwork_1.2.0 lubridate_1.9.3 forcats_1.0.0 stringr_1.5.1
[8] dplyr_1.1.4 purrr_1.0.2 readr_2.1.5 tidyr_1.3.1 tibble_3.2.1 ggplot2_3.5.1 tidyverse_2.0.0
loaded via a namespace (and not attached):
[1] RColorBrewer_1.1-3 rstudioapi_0.16.0 jsonlite_1.8.8 magrittr_2.0.3 spatstat.utils_3.1-0 farver_2.1.2
[7] rmarkdown_2.28 vctrs_0.6.5 ROCR_1.0-11 spatstat.explore_3.3-2 htmltools_0.5.8.1 sass_0.4.9
[13] sctransform_0.4.1 parallelly_1.38.0 KernSmooth_2.23-24 bslib_0.8.0 htmlwidgets_1.6.4 ica_1.0-3
[19] plyr_1.8.9 plotly_4.10.4 zoo_1.8-12 cachem_1.1.0 igraph_2.0.3 mime_0.12
[25] lifecycle_1.0.4 pkgconfig_2.0.3 Matrix_1.7-0 R6_2.5.1 fastmap_1.2.0 fitdistrplus_1.2-1
[31] future_1.34.0 shiny_1.9.1 digest_0.6.37 colorspace_2.1-1 rprojroot_2.0.4 tensor_1.5
[37] RSpectra_0.16-2 irlba_2.3.5.1 labeling_0.4.3 progressr_0.14.0 fansi_1.0.6 spatstat.sparse_3.1-0
[43] timechange_0.3.0 httr_1.4.7 polyclip_1.10-7 abind_1.4-5 compiler_4.4.1 withr_3.0.1
[49] fastDummies_1.7.4 MASS_7.3-61 tools_4.4.1 lmtest_0.9-40 httpuv_1.6.15 future.apply_1.11.2
[55] goftest_1.2-3 glue_1.7.0 nlme_3.1-166 promises_1.3.0 grid_4.4.1 Rtsne_0.17
[61] cluster_2.1.6 reshape2_1.4.4 generics_0.1.3 gtable_0.3.5 spatstat.data_3.1-2 tzdb_0.4.0
[67] data.table_1.16.0 hms_1.1.3 utf8_1.2.4 spatstat.geom_3.3-2 RcppAnnoy_0.0.22 ggrepel_0.9.5
[73] RANN_2.6.2 pillar_1.9.0 spam_2.10-0 RcppHNSW_0.6.0 later_1.3.2 splines_4.4.1
[79] lattice_0.22-6 renv_1.0.7 survival_3.7-0 deldir_2.0-4 tidyselect_1.2.1 miniUI_0.1.1.1
[85] pbapply_1.7-2 knitr_1.48 gridExtra_2.3 scattermore_1.2 xfun_0.47 matrixStats_1.3.0
[91] stringi_1.8.4 lazyeval_0.2.2 yaml_2.3.10 evaluate_0.24.0 codetools_0.2-20 BiocManager_1.30.25
[97] cli_3.6.3 uwot_0.2.2 xtable_1.8-4 reticulate_1.38.0 munsell_0.5.1 jquerylib_0.1.4
[103] Rcpp_1.0.13 globals_0.16.3 spatstat.random_3.3-1 png_0.1-8 spatstat.univar_3.0-0 parallel_4.4.1
[109] dotCall64_1.1-1 listenv_0.9.1 viridisLite_0.4.2 scales_1.3.0 ggridges_0.5.6 leiden_0.4.3.1
[115] rlang_1.1.4 cowplot_1.1.3
LS0tCnRpdGxlOiAiQ29tcGFyZSBsYWJlbCB0cmFuc2ZlciByZXN1bHRzIGJldHdlZW4gQXppbXV0aCBhbmQgQXppbXV0aC1hZGFwdGVkIHN0cmF0ZWd5IgphdXRob3I6IFN0ZXBoYW5pZSBTcGllbG1hbiwgRGF0YSBMYWIKb3V0cHV0OiAKICBodG1sX25vdGVib29rOiAKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwpwYXJhbXM6CiAgc2VlZDogMTIzNDUKLS0tCgoKVGhlIGdvYWwgb2YgdGhpcyBub3RlYm9vayBpcyB0byBjb21wYXJlIGxhYmVsIHRyYW5zZmVyIHJlc3VsdHMgYmV0d2VlbjoKCi0gTGFiZWwgdHJhbnNmZXIgY29kZSB3aXRoIEF6aW11dGggY3VycmVudGx5IGluIGBtYWluYCBhdCBjb21taXQgYDZhZjExMmRgLiBUaGVzZSByZXN1bHRzIGFyZSByZWZlcnJlZCB0byBhcyBgImF6aW11dGgiYC4KLSBMYWJlbCB0cmFuc2ZlciBjb2RlIGFkYXB0ZWQgZnJvbSBBemltdXRoLiBUaGVzZSByZXN1bHRzIGFyZSByZWZlcnJlZCB0byBhcyBgImFkYXB0ZWRfYXppbXV0aCJgLgoKCiMjIFNldHVwCgpgYGB7ciBzZXR1cH0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KG1lc3NhZ2UgPSBGQUxTRSwgd2FybmluZyA9IEZBTFNFKQpvcHRpb25zKGZ1dHVyZS5nbG9iYWxzLm1heFNpemUgPSA4OTEyODk2MDAwMDAwMDApCgpzdXBwcmVzc1BhY2thZ2VTdGFydHVwTWVzc2FnZXMoewogIGxpYnJhcnkodGlkeXZlcnNlKQogIGxpYnJhcnkocGF0Y2h3b3JrKQogIGxpYnJhcnkoU2V1cmF0KQp9KQoKcmVwb3NpdG9yeV9iYXNlIDwtIHJwcm9qcm9vdDo6ZmluZF9yb290KHJwcm9qcm9vdDo6aXNfZ2l0X3Jvb3QpCm1vZHVsZV9iYXNlIDwtIGZpbGUucGF0aChyZXBvc2l0b3J5X2Jhc2UsICJhbmFseXNlcyIsICJjZWxsLXR5cGUtd2lsbXMtdHVtb3ItMDYiKSAgCnJlc3VsdF9kaXIgPC0gZmlsZS5wYXRoKG1vZHVsZV9iYXNlLCAicmVzdWx0cyIpCgoKIyBmdW5jdGlvbnMgdG8gcGVyZm9ybSBsYWJlbCB0cmFuc2ZlciB3aXRoIGF6aW11dGgtYWRhcHRlZCBhcHByb2FjaApzb3VyY2UoCiAgZmlsZS5wYXRoKG1vZHVsZV9iYXNlLCAibm90ZWJvb2tfdGVtcGxhdGUiLCAidXRpbHMiLCAibGFiZWwtdHJhbnNmZXItZnVuY3Rpb25zLlIiKQopCgojIE91dHB1dCBmaWxlcwpmdWxsX3Jlc3VsdHNfZmlsZSA8LSBmaWxlLnBhdGgobW9kdWxlX2Jhc2UsICJzY3JhdGNoIiwgImNvbXBhcmUtbGFiZWwtdHJhbnNmZXJfZmV0YWwtZnVsbC5yZHMiKQpraWRuZXlfcmVzdWx0c19maWxlIDwtIGZpbGUucGF0aChtb2R1bGVfYmFzZSwgInNjcmF0Y2giLCAiY29tcGFyZS1sYWJlbC10cmFuc2Zlcl9mZXRhbC1raWRuZXkucmRzIikKYGBgCgojIyBGdW5jdGlvbnMKCmBgYHtyIGZ1bmN0aW9uc30KIyBNYWtlIGEgaGVhdG1hcCBvZiBjb3VudHMgZm9yIGxhYmVsIHRyYW5zZmVyIHN0cmF0ZWdpZXMKcGxvdF9jb3VudF9oZWF0bWFwIDwtIGZ1bmN0aW9uKGRmLCB0aXRsZSwgc2FtcGxlX2lkKSB7CiAgYWxsX3ByZWRzIDwtIHVuaW9uKGRmJGF6aW11dGgsIGRmJGFkYXB0ZWRfYXppbXV0aCkKICAKICBwbG90bWUgPC0gZGF0YS5mcmFtZSgKICAgIGF6aW11dGggPSBhbGxfcHJlZHMsIAogICAgYWRhcHRlZF9hemltdXRoID0gYWxsX3ByZWRzCiAgKSB8PiAKICAgIGV4cGFuZChhemltdXRoLCBhZGFwdGVkX2F6aW11dGgpIHw+CiAgICBtdXRhdGUobiA9IE5BX2ludGVnZXJfKSB8PgogICAgYW50aV9qb2luKGRpc3RpbmN0KGRmKSkgfD4KICAgIGJpbmRfcm93cygKICAgICAgZGYgfD4gY291bnQoYXppbXV0aCwgYWRhcHRlZF9hemltdXRoKQogICAgKSB8PiAKICAgIGFycmFuZ2UoYXppbXV0aCkgfD4KICAgIG11dGF0ZSgKICAgICAgY29sb3IgPSBjYXNlX3doZW4oCiAgICAgICAgaXMubmEobikgfiAid2hpdGUiLCAKICAgICAgICBuIDw9IDIwIH4gImdyZXk5MCIsCiAgICAgICAgbiA8PSA1MCB+ICJsaWdodGJsdWUiLAogICAgICAgIG4gPD0gMTAwIH4gImNvcm5mbG93ZXJibHVlIiwgCiAgICAgICAgbiA8PSA1MDAgfiAicmVkIiwKICAgICAgICBuIDw9IDEwMDAgfiAieWVsbG93MiIsCiAgICAgICAgLmRlZmF1bHQgPSAieWVsbG93IgogICAgICApCiAgICApCgogICAgZ2dwbG90KHBsb3RtZSkgKyAKICAgICAgYWVzKHggPSBhemltdXRoLCB5ID0gYWRhcHRlZF9hemltdXRoLCBmaWxsID0gY29sb3IsIGxhYmVsID0gbikgKyAKICAgICAgZ2VvbV90aWxlKGFscGhhID0gMC41KSArIAogICAgICBnZW9tX2FibGluZShjb2xvciA9ICJmaXJlYnJpY2siLCBhbHBoYSA9IDAuNSkgKwogICAgICBnZW9tX3RleHQoc2l6ZSA9IDMuNSkgKyAKICAgICAgI3NjYWxlX2ZpbGxfdmlyaWRpc19jKG5hbWUgPSAiY291bnQiLCBuYS52YWx1ZSA9ICJncmV5OTAiKSArCiAgICAgIHNjYWxlX2ZpbGxfaWRlbnRpdHkoKSArCiAgICAgIHRoZW1lX2J3KCkgKyAKICAgICAgdGhlbWUoYXhpcy50ZXh0LnkgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDcpLCAKICAgICAgICAgICAgYXhpcy50ZXh0LnggPSBlbGVtZW50X3RleHQoYW5nbGUgPSAzMCwgc2l6ZSA9IDcsIGhqdXN0PTEpLCAKICAgICAgICAgICAgbGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIsIAogICAgICAgICAgICBsZWdlbmQudGl0bGUgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDkpLCAKICAgICAgICAgICAgbGVnZW5kLnRleHQgPSBlbGVtZW50X3RleHQoc2l6ZSA9IDgpKSArCiAgICAgIGxhYnMoCiAgICAgICAgdGl0bGUgPSBnbHVlOjpnbHVlKCJ7c2FtcGxlX2lkfToge3N0cl90b190aXRsZSh0aXRsZSl9IikKICAgICAgKQp9CgoKIyBXcmFwcGVyIGZ1bmN0aW9uIHRvIGNvbXBhcmUgcmVzdWx0cyBiZXR3ZWVuIGFwcHJvYWNoZXMKIyBNYWtlcyB0d28gcGxvdHM6CiMgLSBoZWF0bWFwIGNvbXBhcmluZyBjb3VudHMgZm9yIGNlbGwgbGFiZWxzIGJldHdlZW4gYXBwcm9hY2hlcwojIC0gZGVuc2l0eSBwbG90IG9mIGFubm90YXRpb24gc2NvcmVzIGZvciBsYWJlbHMgdGhhdCBhZ3JlZSBhbmQgZGlzYWdyZWUgYmV0d2VlbiBhcHByb2FjaGVzCmNvbXBhcmUgPC0gZnVuY3Rpb24oZGYsIGNvbXBhcmVfY29sdW1uLCBzY29yZV9jb2x1bW4sIHRpdGxlKSB7CiAgCiAgc3ByZWFkX2RmIDwtIGRmIHw+CiAgICBzZWxlY3Qoe3tjb21wYXJlX2NvbHVtbn19LCBiYXJjb2RlLCB2ZXJzaW9uKSB8PgogICAgcGl2b3Rfd2lkZXIobmFtZXNfZnJvbSA9IHZlcnNpb24sIHZhbHVlc19mcm9tID0ge3tjb21wYXJlX2NvbHVtbn19KQoKICAKICBoZWF0bWFwIDwtIHBsb3RfY291bnRfaGVhdG1hcChzcHJlYWRfZGYsIHRpdGxlLCB1bmlxdWUoZGYkc2FtcGxlX2lkKSkgIAogIAogIGRpc2FncmVlX2JhcmNvZGVzIDwtIHNwcmVhZF9kZiB8PgogICAgZmlsdGVyKGF6aW11dGggIT0gYWRhcHRlZF9hemltdXRoKSB8PgogICAgcHVsbChiYXJjb2RlKQoKICBkZjIgPC0gZGYgfD4KICAgIG11dGF0ZSgKICAgICAgYWdyZWUgPSBpZmVsc2UoYmFyY29kZSAlaW4lIGRpc2FncmVlX2JhcmNvZGVzLCAibGFiZWxzIGRpc2FncmVlIiwgImxhYmVscyBhZ3JlZSIpLAogICAgICBhZ3JlZSA9IGZjdF9yZWxldmVsKGFncmVlLCAibGFiZWxzIGRpc2FncmVlIiwgImxhYmVscyBhZ3JlZSIpCiAgICApIAogIAogIGRlbnNpdHlfcGxvdCA8LSBnZ3Bsb3QoZGYyKSArIAogICAgYWVzKHggPSB7e3Njb3JlX2NvbHVtbn19LCBmaWxsID0gYWdyZWUpICsgCiAgICBnZW9tX2RlbnNpdHkoYWxwaGEgPSAwLjYpICsgCiAgICB0aGVtZV9idygpICsKICAgIGdndGl0bGUoCiAgICAgIGdsdWU6OmdsdWUoIkRpc2FncmVlIGNvdW50OiB7bGVuZ3RoKGRpc2FncmVlX2JhcmNvZGVzKX0gb3V0IG9mIHtucm93KHNwcmVhZF9kZil9IikKICAgICkgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gImJvdHRvbSIpCgogIHByaW50KGhlYXRtYXAgKyBkZW5zaXR5X3Bsb3QgKyBwbG90X2xheW91dCh3aWR0aHMgPSBjKDIsIDEpKSkKCn0KYGBgCgoKIyMgTGFiZWwgdHJhbnNmZXIKClRoaXMgc2VjdGlvbiBib3RoOgoKLSBSZWFkcyBpbiBleGlzdGluZyBBemltdXRoIGxhYmVsIHRyYW5zZmVyIHJlc3VsdHMKLSBQZXJmb3JtcyBsYWJlbCB0cmFuc2ZlciB3aXRoIEF6aW11dGgtYWRhcHRlZCBhcHByb2FjaAoKSWYgcmVzdWx0cyBhcmUgYWxyZWFkeSBhdmFpbGFibGUsIHdlIHJlYWQgaW4gdGhlIGZpbGVzIHJhdGhlciB0aGFuIHJlZ2VuZXJhdGluZyByZXN1bHRzLgoKYGBge3J9CiMgc2FtcGxlIGlkcyB0byBwcm9jZXNzCnNhbXBsZV9pZHMgPC0gYygiU0NQQ1MwMDAxNzkiLCAiU0NQQ1MwMDAxODQiLCAiU0NQQ1MwMDAxOTQiLCAiU0NQQ1MwMDAyMDUiLCAiU0NQQ1MwMDAyMDgiKQoKIyByZWFkIGluIHNldXJhdCBpbnB1dCBvYmplY3RzLCBhcyBuZWVkZWQKaWYgKCghZmlsZS5leGlzdHMoZnVsbF9yZXN1bHRzX2ZpbGUpKSB8fCAoIWZpbGUuZXhpc3RzKGtpZG5leV9yZXN1bHRzX2ZpbGUpKSkgewogIHNyYXRfb2JqZWN0cyA8LSBzYW1wbGVfaWRzIHw+CiAgICBwdXJycjo6bWFwKAogICAgICBcKGlkKSB7CiAgICAgICAgc3JhdCA8LSByZWFkUkRTKAogICAgICAgICAgZmlsZS5wYXRoKHJlc3VsdF9kaXIsIGlkLCBnbHVlOjpnbHVlKCIwMS1TZXVyYXRfe2lkfS5SZHMiKQogICAgICAgICkpCiAgICAgICAgRGVmYXVsdEFzc2F5KHNyYXQpIDwtICJSTkEiCiAgICAgICAgCiAgICAgICAgcmV0dXJuKHNyYXQpCiAgICB9KQogIG5hbWVzKHNyYXRfb2JqZWN0cykgPC0gc2FtcGxlX2lkcwp9CmBgYAoKCiMjIyBMYWJlbCB0cmFuc2ZlciBmb3IgZmV0YWwgZnVsbAoKYGBge3J9CmlmICghZmlsZS5leGlzdHMoZnVsbF9yZXN1bHRzX2ZpbGUpKSB7CiAgCiAgIyByZWFkIHJlZmVyZW5jZQogIHJlZiA8LSByZWFkUkRTKGZpbGUucGF0aCgKICBtb2R1bGVfYmFzZSwKICAgICJyZXN1bHRzIiwKICAgICJyZWZlcmVuY2VzIiwKICAgICJjYW9fZm9ybWF0dGVkX3JlZi5yZHMiCiAgKSkKICBmdWxsX3JlZmVyZW5jZSA8LSByZWYkcmVmZXJlbmNlCiAgZnVsbF9yZWZkYXRhIDwtIHJlZiRyZWZkYXRhCiAgZnVsbF9kaW1zIDwtIHJlZiRkaW1zCiAgZnVsbF9hbm5vdGF0aW9uX2NvbHVtbnMgPC0gYygKICAgIGdsdWU6OmdsdWUoInByZWRpY3RlZC57cmVmJGFubm90YXRpb25fbGV2ZWxzfSIpLAogICAgZ2x1ZTo6Z2x1ZSgicHJlZGljdGVkLntyZWYkYW5ub3RhdGlvbl9sZXZlbHN9LnNjb3JlIikKICApCgogIAogIGZldGFsX2Z1bGwgPC0gc3JhdF9vYmplY3RzIHw+CiAgICBwdXJycjo6aW1hcCgKICAgICAgXChzcmF0LCBpZCkgewogICAgICAgIAogICAgICAgICMgUGVyZm9ybSBsYWJlbCB0cmFuc2ZlciB3aXRoIG5ldyBjb2RlCiAgICAgICAgc2V0LnNlZWQocGFyYW1zJHNlZWQpCiAgICAgICAgcXVlcnkgPC0gcHJlcGFyZV9xdWVyeShzcmF0LCByb3duYW1lcyhmdWxsX3JlZmVyZW5jZSksIGZpbGUucGF0aChtb2R1bGVfYmFzZSwgInNjcmF0Y2giLCAiaG9tb2xvZ3MucmRzIikpCiAgICAgICAgcXVlcnkgPC0gdHJhbnNmZXJfbGFiZWxzKAogICAgICAgICAgcXVlcnksCiAgICAgICAgICBmdWxsX3JlZmVyZW5jZSwKICAgICAgICAgIGZ1bGxfZGltcywKICAgICAgICAgIGZ1bGxfcmVmZGF0YQogICAgICAgICkKICAgICAgICAKICAgICAgICAjIFJlYWQgaW4gcmVzdWx0cyBmcm9tIGV4aXN0aW5nIEF6aW11dGggbGFiZWwgdHJhbnNmZXIgY29kZQogICAgICAgIHNyYXRfMDJhIDwtIHJlYWRSRFMoCiAgICAgICAgICBmaWxlLnBhdGgocmVzdWx0X2RpciwgaWQsIGdsdWU6OmdsdWUoIjAyYS1mZXRhbF9mdWxsX2xhYmVsLXRyYW5zZmVyX3tpZH0uUmRzIikpCiAgICAgICAgKQogICAgICAgIAogICAgICAgICMgY3JlYXRlIGZpbmFsIGRhdGEgZnJhbWUgd2l0aCBhbGwgYW5ub3RhdGlvbnMKICAgICAgICBxdWVyeUBtZXRhLmRhdGFbLCBmdWxsX2Fubm90YXRpb25fY29sdW1uc10gfD4gCiAgICAgICAgICB0aWJibGU6OnJvd25hbWVzX3RvX2NvbHVtbih2YXIgPSAiYmFyY29kZSIpIHw+CiAgICAgICAgICBtdXRhdGUoCiAgICAgICAgICAgIHNhbXBsZV9pZCA9IGlkLCAKICAgICAgICAgICAgdmVyc2lvbiA9ICJhZGFwdGVkX2F6aW11dGgiCiAgICAgICAgICApIHw+CiAgICAgICAgICAjIGV4aXN0aW5nIHJlc3VsdHMKICAgICAgICAgIGJpbmRfcm93cygKICAgICAgICAgICAgZGF0YS5mcmFtZSgKICAgICAgICAgICAgICBzYW1wbGVfaWQgPSBpZCwKICAgICAgICAgICAgICBiYXJjb2RlID0gY29sbmFtZXMoc3JhdF8wMmEpLAogICAgICAgICAgICAgIHZlcnNpb24gPSAiYXppbXV0aCIsIAogICAgICAgICAgICAgIHByZWRpY3RlZC5hbm5vdGF0aW9uLmwxID0gc3JhdF8wMmEkZmV0YWxfZnVsbF9wcmVkaWN0ZWQuYW5ub3RhdGlvbi5sMSwgCiAgICAgICAgICAgICAgcHJlZGljdGVkLmFubm90YXRpb24ubDEuc2NvcmUgPSBzcmF0XzAyYSRmZXRhbF9mdWxsX3ByZWRpY3RlZC5hbm5vdGF0aW9uLmwxLnNjb3JlLAogICAgICAgICAgICAgIHByZWRpY3RlZC5hbm5vdGF0aW9uLmwyID0gc3JhdF8wMmEkZmV0YWxfZnVsbF9wcmVkaWN0ZWQuYW5ub3RhdGlvbi5sMiwgCiAgICAgICAgICAgICAgcHJlZGljdGVkLmFubm90YXRpb24ubDIuc2NvcmUgPSBzcmF0XzAyYSRmZXRhbF9mdWxsX3ByZWRpY3RlZC5hbm5vdGF0aW9uLmwyLnNjb3JlLAogICAgICAgICAgICAgIHByZWRpY3RlZC5vcmdhbiA9IHNyYXRfMDJhJGZldGFsX2Z1bGxfcHJlZGljdGVkLm9yZ2FuLCAKICAgICAgICAgICAgICBwcmVkaWN0ZWQub3JnYW4uc2NvcmUgPSBzcmF0XzAyYSRmZXRhbF9mdWxsX3ByZWRpY3RlZC5vcmdhbi5zY29yZQogICAgICAgICAgICApIAogICAgICAgICAgKQogICAgICB9CiAgICApCiAgd3JpdGVfcmRzKGZldGFsX2Z1bGwsIGZ1bGxfcmVzdWx0c19maWxlKQp9IGVsc2UgewogIGZldGFsX2Z1bGwgPC0gcmVhZF9yZHMoZnVsbF9yZXN1bHRzX2ZpbGUpCn0KYGBgCgoKIyMjIExhYmVsIHRyYW5zZmVyIGZvciBmZXRhbCBraWRuZXkKCgpgYGB7cn0KaWYgKCFmaWxlLmV4aXN0cyhraWRuZXlfcmVzdWx0c19maWxlKSkgewogIAogIAogICMgcmVhZCByZWZlcmVuY2UKICByZWYgPC0gcmVhZFJEUyhmaWxlLnBhdGgoCiAgICBtb2R1bGVfYmFzZSwKICAgICJyZXN1bHRzIiwKICAgICJyZWZlcmVuY2VzIiwKICAgICJzdGV3YXJ0X2Zvcm1hdHRlZF9yZWYucmRzIgogICkpCiAgCiAgIyBQdWxsIG91dCBpbmZvcm1hdGlvbiBmcm9tIHRoZSByZWZlcmVuY2Ugb2JqZWN0IHdlIG5lZWQgZm9yIGxhYmVsIHRyYW5zZmVyCiAga2lkbmV5X3JlZmVyZW5jZSA8LSByZWYkcmVmZXJlbmNlCiAga2lkbmV5X3JlZmRhdGEgPC0gcmVmJHJlZmRhdGEKICBraWRuZXlfZGltcyA8LSByZWYkZGltcwogIGtpZG5leV9hbm5vdGF0aW9uX2NvbHVtbnMgPC0gYygKICAgIGdsdWU6OmdsdWUoInByZWRpY3RlZC57cmVmJGFubm90YXRpb25fbGV2ZWxzfSIpLAogICAgZ2x1ZTo6Z2x1ZSgicHJlZGljdGVkLntyZWYkYW5ub3RhdGlvbl9sZXZlbHN9LnNjb3JlIikKICApCiAgCiAgCiAgZmV0YWxfa2lkbmV5IDwtIHNyYXRfb2JqZWN0cyB8PgogICAgcHVycnI6OmltYXAoCiAgICAgIFwoc3JhdCwgaWQpIHsKICAgICAgICAKICAgICAgICAjIFBlcmZvcm0gbGFiZWwgdHJhbnNmZXIgd2l0aCBuZXcgY29kZQogICAgICAgIHNldC5zZWVkKHBhcmFtcyRzZWVkKQogICAgICAgIHF1ZXJ5IDwtIHByZXBhcmVfcXVlcnkoc3JhdCwgcm93bmFtZXMoa2lkbmV5X3JlZmVyZW5jZSksIGZpbGUucGF0aChtb2R1bGVfYmFzZSwgInNjcmF0Y2giLCAiaG9tb2xvZ3MucmRzIikpCiAgICAgICAgcXVlcnkgPC0gdHJhbnNmZXJfbGFiZWxzKAogICAgICAgICAgcXVlcnksCiAgICAgICAgICBraWRuZXlfcmVmZXJlbmNlLAogICAgICAgICAga2lkbmV5X2RpbXMsCiAgICAgICAgICBraWRuZXlfcmVmZGF0YQogICAgICAgICkKICAgICAgICAKICAgICAgICAjIFJlYWQgaW4gcmVzdWx0cyBmcm9tIGV4aXN0aW5nIEF6aW11dGggbGFiZWwgdHJhbnNmZXIgY29kZQogICAgICAgIHNyYXRfMDJiIDwtIHJlYWRSRFMoCiAgICAgICAgICBmaWxlLnBhdGgocmVzdWx0X2RpciwgaWQsIGdsdWU6OmdsdWUoIjAyYi1mZXRhbF9raWRuZXlfbGFiZWwtdHJhbnNmZXJfe2lkfS5SZHMiKSkKICAgICAgICApCiAgICAgICAgCiAgICAgICAgIyBjcmVhdGUgZmluYWwgZGF0YSBmcmFtZSB3aXRoIGFsbCBhbm5vdGF0aW9ucwogICAgICAgIHF1ZXJ5QG1ldGEuZGF0YVssIGtpZG5leV9hbm5vdGF0aW9uX2NvbHVtbnNdIHw+IAogICAgICAgICAgdGliYmxlOjpyb3duYW1lc190b19jb2x1bW4odmFyID0gImJhcmNvZGUiKSB8PgogICAgICAgICAgbXV0YXRlKAogICAgICAgICAgICBzYW1wbGVfaWQgPSBpZCwgCiAgICAgICAgICAgIHZlcnNpb24gPSAiYWRhcHRlZF9hemltdXRoIgogICAgICAgICAgKSB8PgogICAgICAgICAgIyBleGlzdGluZyByZXN1bHRzCiAgICAgICAgICBiaW5kX3Jvd3MoCiAgICAgICAgICAgIGRhdGEuZnJhbWUoCiAgICAgICAgICAgICAgc2FtcGxlX2lkID0gaWQsCiAgICAgICAgICAgICAgYmFyY29kZSA9IGNvbG5hbWVzKHNyYXRfMDJiKSwKICAgICAgICAgICAgICB2ZXJzaW9uID0gImF6aW11dGgiLCAKICAgICAgICAgICAgICBwcmVkaWN0ZWQuY29tcGFydG1lbnQgPSBzcmF0XzAyYiRmZXRhbF9raWRuZXlfcHJlZGljdGVkLmNvbXBhcnRtZW50LCAKICAgICAgICAgICAgICBwcmVkaWN0ZWQuY29tcGFydG1lbnQuc2NvcmUgPSBzcmF0XzAyYiRmZXRhbF9raWRuZXlfcHJlZGljdGVkLmNvbXBhcnRtZW50LnNjb3JlLAogICAgICAgICAgICAgIHByZWRpY3RlZC5jZWxsX3R5cGUgPSBzcmF0XzAyYiRmZXRhbF9raWRuZXlfcHJlZGljdGVkLmNlbGxfdHlwZSwgCiAgICAgICAgICAgICAgcHJlZGljdGVkLmNlbGxfdHlwZS5zY29yZSA9IHNyYXRfMDJiJGZldGFsX2tpZG5leV9wcmVkaWN0ZWQuY2VsbF90eXBlLnNjb3JlCiAgICAgICAgICAgICkgCiAgICAgICAgICApCiAgICAgIH0KICAgICkKCiAgd3JpdGVfcmRzKGZldGFsX2tpZG5leSwga2lkbmV5X3Jlc3VsdHNfZmlsZSkKfSBlbHNlIHsKICBmZXRhbF9raWRuZXkgPC0gcmVhZF9yZHMoa2lkbmV5X3Jlc3VsdHNfZmlsZSkKfQpgYGAKCgojIyBDb21wYXJlIHJlc3VsdHMKCldlIGV4cGVjdDoKLSBUaGUgbWFqb3JpdHkgb2YgYW5ub3RhdGlvbnMgbWF0Y2ggYmV0d2VlbiBhcHByb2FjaGVzLCB3aXRoIGhlYXRtYXAgY291bnRzIHByaW1hcmlseSBmYWxsaW5nIGFsb25nIHRoZSBkaWFnb25hbAotIEFueSBhbm5vdGF0aW9ucyB0aGF0IGRpc2FncmVlIHNob3VsZCBoYXZlIGxvdyBzY29yZXMKCgojIyMgRmV0YWwgZnVsbCByZWZlcmVuY2UKCk5vdGUgdGhhdCByZXN1bHRzIGZyb20gdGhlIEwyIHJlZmVyZW5jZSBhcmUgbm90IHBsb3R0ZWQgYmVjYXVzZSB0aGV5IGFyZSBub3QgdXNlZCBpbiBjZWxsIHR5cGUgYW5ub3RhdGlvbi4KCgpgYGB7ciBmaWcuaGVpZ2h0PTgsIGZpZy53aWR0aD0xNH0KZmV0YWxfZnVsbCB8PgogIHB1cnJyOjp3YWxrKAogICAgXChkYXQpIHsKICAgICAgY29tcGFyZShkYXQsIHByZWRpY3RlZC5hbm5vdGF0aW9uLmwxLCBwcmVkaWN0ZWQuYW5ub3RhdGlvbi5sMS5zY29yZSwgImwxIikKICAgICAgY29tcGFyZShkYXQsIHByZWRpY3RlZC5vcmdhbiwgcHJlZGljdGVkLm9yZ2FuLnNjb3JlLCAib3JnYW4iKQogICAgfQogICkKYGBgCgoKIyMjIEZldGFsIGtpZG5leSByZWZlcmVuY2UKCmBgYHtyIGZpZy5oZWlnaHQ9OCwgZmlnLndpZHRoPTE0fQpmZXRhbF9raWRuZXkgfD4KICBwdXJycjo6d2FsaygKICAgIFwoZGF0KSB7CiAgICAgIGNvbXBhcmUoZGF0LCBwcmVkaWN0ZWQuY29tcGFydG1lbnQsIHByZWRpY3RlZC5jb21wYXJ0bWVudC5zY29yZSwgImNvbXBhcnRtZW50IikKICAgICAgY29tcGFyZShkYXQsIHByZWRpY3RlZC5jZWxsX3R5cGUsIHByZWRpY3RlZC5jZWxsX3R5cGUuc2NvcmUsICJjZWxsX3R5cGUiKQogICAgfQogICkKYGBgCgoKCiMjIENvbmNsdXNpb25zCgpUaGUgdmFzdCBtYWpvcml0eSBvZiB0aGUgdGltZSwgbGFiZWxzIGFncmVlLiAKR2VuZXJhbGx5IHNwZWFraW5nLCB3aGVuIGxhYmVscyBkbyBub3QgYWdyZWUsIHRoZWlyIGFubm90YXRpb24gc2NvcmVzIGFyZSBtdWNoIGxvd2VyLCB3aGljaCBpcyBhcyBleHBlY3RlZC4KIApBZGRpdGlvbmFsIG5vdGFibGUgZGlmZmVyZW5jZXMgYXJlIHNob3duIGluIHRhYmxlcyBiZWxvdzoKICAgIAojIyMgRmV0YWwgZnVsbCByZWZlcmVuY2U6CgotIFRoZSBBemltdXRoLWFkYXB0ZWQgYXBwcm9hY2ggb2NjYXNpb25hbGx5IGNhbGxzIGtpZG5leSBvciBraWRuZXktcmVsYXRlZCBjZWxscyBhcyBpbnRlc3RpbmUgb3IgaW50ZXN0aW5lIGVwaXRoZWxpYWwKLSBTb21lIG90aGVyIGtpZG5leS1yZWxhdGVkIGRpZmZlcmVuY2VzIGFyZSBub3RlZDoKCnwgU2FtcGxlIHwgUmVmZXJlbmNlIHwgQ291bnQgfCBBemltdXRoIHwgQXppbXV0aC1hZGFwdGVkIHwKfC0tLS0tLS0tfC0tLS0tLS0tLS0tfC0tLS0tLS18LS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tfAp8IFNDUFNDMDAwMTc5IHwgTDEgfCA3MCB8IE1ldGFuZXBocml0aWMgY2VsbHMgfCBJbnRlc3RpbmFsIGVwaXRoZWxpYWwgY2VsbHMgfCAKfCBTQ1BTQzAwMDE3OSB8IE9yZ2FuIHwgNjQgfCBLaWRuZXkgfCBJbnRlc3RpbmUgfCAKfCBTQ1BTQzAwMDE3OSB8IE9yZ2FuIHwgMjAgfCBMdW5nIHwgS2lkbmV5IHwgCnwgU0NQU0MwMDAxOTQgfCBMMSB8IDYwIHwgU3Ryb21hbCBjZWxscyB8IE1lc2FuZ2lhbCBjZWxscyB8IAp8IFNDUFNDMDAwMTk0IHwgT3JnYW4gfCAzNSB8IEtpZG5leSB8IEludGVzdGluZSB8IAp8IFNDUFNDMDAwMTk0IHwgT3JnYW4gfCAzNiB8IEx1bmcgfCBLaWRuZXkgfCAKfCBTQ1BTQzAwMDIwNSB8IE9yZ2FuIHwgNTYgfCBLaWRuZXkgfCBJbnRlc3RpbmUgfAp8IFNDUFNDMDAwMjA4IHwgTDEgfCAxMDEgfCBNZXNhbmdpYWwgY2VsbHMgfCBNZXRhbmVwaHJpdGljIGNlbGxzIHwgIAp8IFNDUFNDMDAwMjA4IHwgTDEgfCAxMDEgfCBJbnRlc3RpbmFsIGVwaXRoZWxpYWwgY2VsbHMgfCBNZXRhbmVwaHJpdGljIGNlbGxzIHwgIAp8IFNDUFNDMDAwMjA4IHwgT3JnYW4gfCAxNDkgfCBLaWRuZXkgfCBJbnRlc3RpbmUgfCAKCgojIyMgRmV0YWwga2lkbmV5IHJlZmVyZW5jZToKCgotIFRoZXJlIGFyZSBhIHNtYWxsIGJ1dCBub3RhYmxlIG51bWJlciBvZiBjZWxscyBmbGlwcGVkIGJldHdlZW4gbWVzZW5jaHltZSBhbmQga2lkbmV5IGNlbGxzLCBpbiBwYXJ0aWN1bGFyIGZvciBzYW1wbGUgU0NQU0MwMDAxODQuClRoaXMgaXMgdGhlIG1haW4gZGlzY3JlcGFuY3kuCi0gTW9zdCBvZiB0aGUgY2VsbCB0eXBlIGRpZmZlcmVuY2VzIGFyZSBub3QgbmVjZXNzYXJpbHkgYmlvbG9naWNhbGx5IG1lYW5pbmdmdWwgZm9yIG91ciBwdXJwb3NlcywgYXMgbGlzdGVkIGJlbG93LiBUaGVzZSBhcmUgbm90IG5vdGVkIGluIHRoZSB0YWJsZS4KICAgLSBga2lkbmV5IGNlbGxgIHZzIGBwb2RvY3l0ZWAKICAgLSBga2lkbmV5IGVwaXRoZWxpYWwgY2VsbGAgdnMgYGtpZG5leSBjZWxsYCAKICAgLSBgbWVzZW5jaHltYWwgY2VsbGAgdnMgYG1lc2VuY2h5bWFsIHN0ZW0gY2VsbGAgCi0gVGhlcmUgYXJlIGEgZGVjZW50IG51bWJlciBvZiB0aW1lcyB3aGVuIHN0cm9tYSBhbmQgZmV0YWwgbmVwaHJvbiBhcmUgZmxpcHBlZCwgYnV0IHRoaXMgbWFrZXMgc2Vuc2UgZ2l2ZW4gdGhhdCB3ZSBleHBlY3QgbWFueSBvZiB0aGUgc3Ryb21hIG1heSBiZSB0dW1vci4KLSBEaXNhZ3JlZWluZyBhbm5vdGF0aW9uIHNjb3JlcyB3aGVuIHVzaW5nIHRoZSBjZWxsIHR5cGUgcmVmZXJlbmNlIHdlcmUgb2Z0ZW4gaGlnaGVyLCBidXQgbWFueSBvZiB0aGUgZGlzYWdyZWVtZW50cyBmb3IgdGhpcyByZWZlcmVuY2Ugd2VyZSBub3QgbWVhbmluZ2Z1bCAoZS5nLiBga2lkbmV5IGVwaXRoZWxpYWwgY2VsbGAgdnMgYGtpZG5leSBjZWxsYCkuCgoKfCBTYW1wbGUgfCBSZWZlcmVuY2UgfCBDb3VudCB8IEF6aW11dGggfCBBemltdXRoLWFkYXB0ZWQgfAp8LS0tLS0tLS18LS0tLS0tLS0tLS18LS0tLS0tLXwtLS0tLS0tLS18LS0tLS0tLS0tLS0tLS0tLS18CnwgU0NQU0MwMDAxNzkgfCBjZWxsIHR5cGUgfCAyMDIgfCBtZXNlbmNoeW1hbCBjZWxsIHwga2lkbmV5IGVwaXRoZWxpYWwgY2VsbCB8IAp8IFNDUFNDMDAwMTg0IHwgY29tcGFydG1lbnQgfCAxMTEgfCBmZXRhbCBuZXBocm9uIHwgIHN0cm9tYSB8IAp8IFNDUFNDMDAwMTg0IHwgY2VsbCB0eXBlIHwgNTM2IHwga2lkbmV5IGVwaXRoZWxpYWwgY2VsbCB8IG1lc2VuY2h5bWFsIGNlbGwgfCAKfCBTQ1BTQzAwMDE5NCB8IGNvbXBhcnRtZW50IHwgNTY1IHwgZmV0YWwgbmVwaHJvbiB8ICBzdHJvbWEgfCAKfCBTQ1BTQzAwMDE5NCB8IGNlbGwgdHlwZSAgfCA4OSB8IGtpZG5leSBlcGl0aGVsaWFsIGNlbGwgfCBtZXNlbmNoeW1hbCBjZWxsIHwgCnwgU0NQU0MwMDAyMDUgfCBjb21wYXJ0bWVudCAgfCA2ODQgfCBmZXRhbCBuZXBocm9uIHwgIHN0cm9tYSB8IAp8IFNDUFNDMDAwMjA4IHwgY29tcGFydG1lbnQgIHwgMjExMSB8IGZldGFsIG5lcGhyb24gfCAgc3Ryb21hIHwgCgoKIyMgU2Vzc2lvbiBJbmZvCgpgYGB7cn0Kc2Vzc2lvbkluZm8oKQpgYGAK